package cn.bincker.mycaptcha.slider;

import cn.bincker.mycaptcha.CacheManager;
import cn.bincker.mycaptcha.basic.entity.MyQuestion;
import cn.bincker.mycaptcha.basic.properties.CacheConfigProperties;
import cn.bincker.mycaptcha.slider.entity.SliderAnswer;
import cn.bincker.mycaptcha.slider.entity.SliderCaptcha;
import cn.bincker.mycaptcha.utils.FileUtils;
import cn.bincker.mycaptcha.utils.RequestUtils;
import lombok.extern.slf4j.Slf4j;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;

@Slf4j
public class SliderCacheManagerImpl implements CacheManager<SliderCaptcha, SliderAnswer> {
    private final List<CacheInfo> cache;
    private final Random random = new Random();
    private final SliderCaptchaProperties properties;
    private final CacheConfigProperties config;
    private final File captchaPath;
    private final File answerPath;

    public SliderCacheManagerImpl(SliderCaptchaProperties properties) {
        this.properties = properties;
        config = properties.getCache();
        cache = new ArrayList<>(config.getMaxCacheItems());
        captchaPath = config.getCaptchaPath();
        answerPath = config.getAnswerPath();
        if(!captchaPath.exists() && !captchaPath.mkdirs())
            throw new RuntimeException("缓存目录创建失败: path=" + captchaPath.getAbsolutePath());
        if(!answerPath.exists() && !answerPath.mkdirs())
            throw new RuntimeException("缓存目录创建失败: path=" + answerPath.getAbsolutePath());
    }

    @Override
    public void put(MyQuestion<SliderCaptcha, SliderAnswer> question, BufferedImage ...images) {
        File answerFile, captchaImageFile, sliderImageFile;
        var filename = FileUtils.generateNotExistsFilename(answerPath);
        answerFile = new File(answerPath, filename);
        captchaImageFile = new File(captchaPath, filename + "-captcha.png");
        sliderImageFile = new File(captchaPath, filename + "-slider.png");
        try(var out = new FileOutputStream(answerFile)) {
            ImageIO.write(images[0], "png", captchaImageFile);
            ImageIO.write(images[1], "png", sliderImageFile);
            var bytebuffer = ByteBuffer.allocate(8);
            bytebuffer.putInt(question.getAnswer().getX());
            bytebuffer.putInt(question.getCaptcha().getY());
            out.write(bytebuffer.rewind().array());
        } catch (IOException e) {
            deleteCache(answerFile, captchaImageFile, sliderImageFile, "输出失败");
            throw new RuntimeException("输出缓存失败", e);
        }
        //设置地址
        var captcha = question.getCaptcha();
        var requestPath = RequestUtils.getRequestPath();
        captcha.setImage(requestPath + captchaImageFile.getName());
        captcha.setSlider(requestPath + sliderImageFile.getName());
        //缓存的数据不设置地址
        var cacheQuestion = question.clone();
        cacheQuestion.getCaptcha().setImage(captchaImageFile.getName());
        cacheQuestion.getCaptcha().setSlider(sliderImageFile.getName());
        cache.add(new CacheInfo(System.currentTimeMillis() + config.getSurvivalTime(), cacheQuestion, answerFile, captchaImageFile, sliderImageFile));
    }

    @Override
    public Optional<MyQuestion<SliderCaptcha, SliderAnswer>> shooting() {
        while (true) {
            var position = random.nextInt(config.getMaxCacheItems());
            if (position < cache.size()) {
                var cacheInfo = cache.get(position);
                if(cacheInfo.isExpired()) {
                    cache.remove(position);
                    deleteCache(cacheInfo.answerFile, cacheInfo.captchaImage, cacheInfo.sliderImage, "过期");
                    continue;
                }
                var requestPath = RequestUtils.getRequestPath();
                var question = cacheInfo.question;
                var captcha = question.getCaptcha();
                var cloneQuestion = question.clone();
                cloneQuestion.getCaptcha().setImage(requestPath + captcha.getImage());
                cloneQuestion.getCaptcha().setSlider(requestPath + captcha.getSlider());
                return Optional.of(cloneQuestion);
            }
            return Optional.empty();
        }
    }

    @Override
    public void reload() {
        var cacheConfig = properties.getCache();
        var cacheCaptchaPath = cacheConfig.getCaptchaPath();
        var answerFiles = Optional.ofNullable(cacheConfig.getAnswerPath().listFiles())
                .orElse(new File[]{});
        var captchaFiles = new HashSet<>(Arrays.asList(
                Optional.ofNullable(cacheConfig.getCaptchaPath().list())
                        .orElse(new String[]{})
        ));
        for (File answerFile : answerFiles) {
            var captchaImage = new File(cacheCaptchaPath, answerFile.getName() + "-captcha.png");
            var sliderImage = new File(cacheCaptchaPath, answerFile.getName() + "-slider.png");
            long createTime;
            try {
                createTime = Files.readAttributes(Path.of(answerFile.getAbsolutePath()), BasicFileAttributes.class).creationTime().toMillis();
                if(createTime + config.getSurvivalTime() < System.currentTimeMillis()){
                    deleteCache(answerFile, captchaImage, sliderImage, "过期");
                    continue;
                }
            } catch (IOException e) {
                log.warn("读取缓存创建时间失败:\tfile={}", answerFile, e);
                deleteCache(answerFile, captchaImage, sliderImage, "异常");
                continue;
            }
            if(!captchaImage.exists()){
                captchaFiles.remove(sliderImage.getName());
                deleteCache(answerFile, captchaImage, sliderImage, "不完整");
            }else if(!sliderImage.exists()){
                captchaFiles.remove(captchaImage.getName());
                deleteCache(answerFile, captchaImage, sliderImage, "不完整");
            }else{
                cache.add(new CacheInfo(createTime + config.getSurvivalTime(), readCaptcha(answerFile, captchaImage, sliderImage), answerFile, captchaImage, sliderImage));
            }
        }
        //删除多余的文件
        if(!captchaFiles.isEmpty()){
            for (String captchaFile : captchaFiles) {
                new File(cacheCaptchaPath, captchaFile).deleteOnExit();
            }
        }
    }

    private MyQuestion<SliderCaptcha, SliderAnswer> readCaptcha(File answerFile, File captchaImage, File sliderImage) {
        try(var in = new FileInputStream(answerFile)){
            var bytebuffer = ByteBuffer.allocate(8);
            bytebuffer.put(in.readAllBytes());
            if(bytebuffer.position() != 8) throw new EOFException();
            bytebuffer.rewind();
            var x = bytebuffer.getInt();
            var y = bytebuffer.getInt();
            return new MyQuestion<>(
                    new SliderCaptcha(
                            captchaImage.getName(),
                            sliderImage.getName(),
                            y
                    ),
                    new SliderAnswer(x)
            );
        } catch (IOException e) {
            log.warn("读取缓存失败:\tfile={}", answerFile.getAbsolutePath(), e);
            return null;
        }
    }

    private void deleteCache(File answerFile, File captchaImage, File sliderImage, String message){
        log.debug("验证码缓存[{}]因{}被删除", answerFile.getName(), message);
        if(!answerFile.delete()){
            log.warn("删除缓存[{}]失败", answerFile.getAbsolutePath());
        }
        if(captchaImage.exists() && !captchaImage.delete()){
            log.warn("删除缓存[{}]失败", answerFile.getAbsolutePath());
        }
        if(sliderImage.exists() && !sliderImage.delete()){
            log.warn("删除缓存[{}]失败", answerFile.getAbsolutePath());
        }
    }

    private static class CacheInfo{
        private final long expiredTime;
        private final MyQuestion<SliderCaptcha, SliderAnswer> question;
        private final File answerFile;
        private final File captchaImage;
        private final File sliderImage;

        private CacheInfo(long expiredTime, MyQuestion<SliderCaptcha, SliderAnswer> question, File answerFile, File captchaImage, File sliderImage) {
            this.expiredTime = expiredTime;
            this.question = question;
            this.answerFile = answerFile;
            this.captchaImage = captchaImage;
            this.sliderImage = sliderImage;
        }

        private boolean isExpired(){
            return System.currentTimeMillis() > expiredTime;
        }
    }
}
